/**
 * we-cropper v1.3.9
 * (c) 2020 dlhandsome
 * @license MIT
 */
(function(global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory()
    : typeof define === 'function' && define.amd ? define(factory)
      : (global.WeCropper = factory())
}(this, function() {
  'use strict'

  let device = void 0

  const TOUCH_STATE = ['touchstarted', 'touchmoved', 'touchended']

  function firstLetterUpper(str) {
    return str.charAt(0).toUpperCase() + str.slice(1)
  }

  function setTouchState(instance) {
    const arg = []

    let len = arguments.length - 1

    while (len-- > 0) arg[ len ] = arguments[ len + 1 ]

    TOUCH_STATE.forEach(function(key, i) {
      if (arg[ i ] !== undefined) {
        instance[ key ] = arg[ i ]
      }
    })
  }

  function validator(instance, o) {
    Object.defineProperties(instance, o)
  }

  function getDevice() {
    if (!device) {
      device = uni.getSystemInfoSync()
    }
    return device
  }

  const tmp = {}

  const ref = getDevice()

  const pixelRatio = ref.pixelRatio

  const DEFAULT = {
    id: {
      default: 'cropper',
      get: function get() {
        return tmp.id
      },
      set: function set(value) {
        if (typeof (value) !== 'string') {
          console.error(('id：' + value + ' is invalid'))
        }
        tmp.id = value
      }
    },
    width: {
      default: 750,
      get: function get() {
        return tmp.width
      },
      set: function set(value) {
        if (typeof (value) !== 'number') {
          console.error(('width：' + value + ' is invalid'))
        }
        tmp.width = value
      }
    },
    height: {
      default: 750,
      get: function get() {
        return tmp.height
      },
      set: function set(value) {
        if (typeof (value) !== 'number') {
          console.error(('height：' + value + ' is invalid'))
        }
        tmp.height = value
      }
    },
    pixelRatio: {
      default: pixelRatio,
      get: function get() {
        return tmp.pixelRatio
      },
      set: function set(value) {
        if (typeof (value) !== 'number') {
          console.error(('pixelRatio：' + value + ' is invalid'))
        }
        tmp.pixelRatio = value
      }
    },
    scale: {
      default: 2.5,
      get: function get() {
        return tmp.scale
      },
      set: function set(value) {
        if (typeof (value) !== 'number') {
          console.error(('scale：' + value + ' is invalid'))
        }
        tmp.scale = value
      }
    },
    zoom: {
      default: 5,
      get: function get() {
        return tmp.zoom
      },
      set: function set(value) {
        if (typeof (value) !== 'number') {
          console.error(('zoom：' + value + ' is invalid'))
        } else if (value < 0 || value > 10) {
          console.error('zoom should be ranged in 0 ~ 10')
        }
        tmp.zoom = value
      }
    },
    src: {
      default: '',
      get: function get() {
        return tmp.src
      },
      set: function set(value) {
        if (typeof (value) !== 'string') {
          console.error(('src：' + value + ' is invalid'))
        }
        tmp.src = value
      }
    },
    cut: {
      default: {},
      get: function get() {
        return tmp.cut
      },
      set: function set(value) {
        if (typeof (value) !== 'object') {
          console.error(('cut：' + value + ' is invalid'))
        }
        tmp.cut = value
      }
    },
    boundStyle: {
      default: {},
      get: function get() {
        return tmp.boundStyle
      },
      set: function set(value) {
        if (typeof (value) !== 'object') {
          console.error(('boundStyle：' + value + ' is invalid'))
        }
        tmp.boundStyle = value
      }
    },
    onReady: {
      default: null,
      get: function get() {
        return tmp.ready
      },
      set: function set(value) {
        tmp.ready = value
      }
    },
    onBeforeImageLoad: {
      default: null,
      get: function get() {
        return tmp.beforeImageLoad
      },
      set: function set(value) {
        tmp.beforeImageLoad = value
      }
    },
    onImageLoad: {
      default: null,
      get: function get() {
        return tmp.imageLoad
      },
      set: function set(value) {
        tmp.imageLoad = value
      }
    },
    onBeforeDraw: {
      default: null,
      get: function get() {
        return tmp.beforeDraw
      },
      set: function set(value) {
        tmp.beforeDraw = value
      }
    }
  }

  const ref$1 = getDevice()

  const windowWidth = ref$1.windowWidth

  function prepare() {
    const self = this

    // v1.4.0 版本中将不再自动绑定we-cropper实例
    self.attachPage = function() {
      const pages = getCurrentPages()
      // 获取到当前page上下文

      const pageContext = pages[ pages.length - 1 ]
      // 把this依附在Page上下文的wecropper属性上，便于在page钩子函数中访问

      Object.defineProperty(pageContext, 'wecropper', {
        get: function get() {
          console.warn('Instance will not be automatically bound to the page after v1.4.0\n\n' +
						'Please use a custom instance name instead\n\n' +
						'Example: \n' +
						'this.mycropper = new WeCropper(options)\n\n' +
						'// ...\n' +
						'this.mycropper.getCropperImage()')
          return self
        },
        configurable: true
      })
    }

    self.createCtx = function() {
      const id = self.id

      const targetId = self.targetId

      if (id) {
        self.ctx = self.ctx || uni.createCanvasContext(id)
        self.targetCtx = self.targetCtx || uni.createCanvasContext(targetId)
      } else {
        console.error("constructor: create canvas context failed, 'id' must be valuable")
      }
    }

    self.deviceRadio = windowWidth / 750
  }

  const commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !==
		'undefined' ? self : {}

  function createCommonjsModule(fn, module) {
    return module = {
      exports: {}
    }, fn(module, module.exports), module.exports
  }

  const tools = createCommonjsModule(function(module, exports) {
    /**
		 * String type check
		 */
    exports.isStr = function(v) {
      return typeof v === 'string'
    }
    /**
		 * Number type check
		 */
    exports.isNum = function(v) {
      return typeof v === 'number'
    }
    /**
		 * Array type check
		 */
    exports.isArr = Array.isArray
    /**
		 * undefined type check
		 */
    exports.isUndef = function(v) {
      return v === undefined
    }

    exports.isTrue = function(v) {
      return v === true
    }

    exports.isFalse = function(v) {
      return v === false
    }
    /**
		 * Function type check
		 */
    exports.isFunc = function(v) {
      return typeof v === 'function'
    }
    /**
		 * Quick object check - this is primarily used to tell
		 * Objects from primitive values when we know the value
		 * is a JSON-compliant type.
		 */
    exports.isObj = exports.isObject = function(obj) {
      return obj !== null && typeof obj === 'object'
    }

    /**
		 * Strict object type check. Only returns true
		 * for plain JavaScript objects.
		 */
    const _toString = Object.prototype.toString

    exports.isPlainObject = function(obj) {
      return _toString.call(obj) === '[object Object]'
    }

    /**
		 * Check whether the object has the property.
		 */
    const hasOwnProperty = Object.prototype.hasOwnProperty

    exports.hasOwn = function(obj, key) {
      return hasOwnProperty.call(obj, key)
    }

    /**
		 * Perform no operation.
		 * Stubbing args to make Flow happy without leaving useless transpiled code
		 * with ...rest (https://flow.org/blog/2017/05/07/Strict-Function-Call-Arity/)
		 */
    exports.noop = function(a, b, c) {}

    /**
		 * Check if val is a valid array index.
		 */
    exports.isValidArrayIndex = function(val) {
      const n = parseFloat(String(val))

      return n >= 0 && Math.floor(n) === n && isFinite(val)
    }
  })

  const tools_7 = tools.isFunc

  const tools_10 = tools.isPlainObject

  const EVENT_TYPE = ['ready', 'beforeImageLoad', 'beforeDraw', 'imageLoad']

  function observer() {
    const self = this

    self.on = function(event, fn) {
      if (EVENT_TYPE.indexOf(event) > -1) {
        if (tools_7(fn)) {
          event === 'ready'
            ? fn(self)
            : self[ ('on' + (firstLetterUpper(event))) ] = fn
        }
      } else {
        console.error(('event: ' + event + ' is invalid'))
      }
      return self
    }
  }

  function wxPromise(fn) {
    return function(obj) {
      const args = []

      let len = arguments.length - 1

      while (len-- > 0) args[ len ] = arguments[ len + 1 ]

      if (obj === void 0) obj = {}
      return new Promise(function(resolve, reject) {
        obj.success = function(res) {
          resolve(res)
        }
        obj.fail = function(err) {
          reject(err)
        }
        fn.apply(void 0, [obj].concat(args))
      })
    }
  }

  function draw(ctx, reserve) {
    if (reserve === void 0) reserve = false

    return new Promise(function(resolve) {
      ctx.draw(reserve, resolve)
    })
  }

  const getImageInfo = wxPromise(uni.getImageInfo)

  const canvasToTempFilePath = wxPromise(uni.canvasToTempFilePath)

  const base64 = createCommonjsModule(function(module, exports) {
    /*! http://mths.be/base64 v0.1.0 by @mathias | MIT license */
    (function(root) {

      // Detect free variables `exports`.
      const freeExports = 'object' === 'object' && exports

      // Detect free variable `module`.
      const freeModule = 'object' === 'object' && module &&
				module.exports == freeExports && module

      // Detect free variable `global`, from Node.js or Browserified code, and use
      // it as `root`.
      const freeGlobal = typeof commonjsGlobal === 'object' && commonjsGlobal

      if (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal) {
        root = freeGlobal
      }

      /* --------------------------------------------------------------------------*/

      const InvalidCharacterError = function(message) {
        this.message = message
      }

      InvalidCharacterError.prototype = new Error()
      InvalidCharacterError.prototype.name = 'InvalidCharacterError'

      const error = function(message) {
        // Note: the error messages used throughout this file match those used by
        // the native `atob`/`btoa` implementation in Chromium.
        throw new InvalidCharacterError(message)
      }

      const TABLE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
      // http://whatwg.org/html/common-microsyntaxes.html#space-character

      const REGEX_SPACE_CHARACTERS = /[\t\n\f\r ]/g

      // `decode` is designed to be fully compatible with `atob` as described in the
      // HTML Standard. http://whatwg.org/html/webappapis.html#dom-windowbase64-atob
      // The optimized base64-decoding algorithm used is based on @atk’s excellent
      // implementation. https://gist.github.com/atk/1020396
      const decode = function(input) {
        input = String(input)
          .replace(REGEX_SPACE_CHARACTERS, '')
        let length = input.length

        if (length % 4 == 0) {
          input = input.replace(/==?$/, '')
          length = input.length
        }
        if (
          length % 4 == 1 ||
					// http://whatwg.org/C#alphanumeric-ascii-characters
					/[^+a-zA-Z0-9/]/.test(input)
        ) {
          error('Invalid character: the string to be decoded is not correctly encoded.')
        }
        let bitCounter = 0

        let bitStorage

        let buffer

        let output = ''

        let position = -1

        while (++position < length) {
          buffer = TABLE.indexOf(input.charAt(position))
          bitStorage = bitCounter % 4 ? bitStorage * 64 + buffer : buffer
          // Unless this is the first of a group of 4 characters…
          if (bitCounter++ % 4) {
            // …convert the first 8 bits to a single ASCII character.
            output += String.fromCharCode(0xFF & bitStorage >> (-2 * bitCounter & 6))
          }
        }
        return output
      }

      // `encode` is designed to be fully compatible with `btoa` as described in the
      // HTML Standard: http://whatwg.org/html/webappapis.html#dom-windowbase64-btoa
      const encode = function(input) {
        input = String(input)
        if (/[^\0-\xFF]/.test(input)) {
          // Note: no need to special-case astral symbols here, as surrogates are
          // matched, and the input is supposed to only contain ASCII anyway.
          error('The string to be encoded contains characters outside of the ' +
						'Latin1 range.')
        }
        const padding = input.length % 3

        let output = ''

        let position = -1

        let a

        let b

        let c

        let buffer
        // Make sure any padding is handled outside of the loop.

        const length = input.length - padding

        while (++position < length) {
          // Read three bytes, i.e. 24 bits.
          a = input.charCodeAt(position) << 16
          b = input.charCodeAt(++position) << 8
          c = input.charCodeAt(++position)
          buffer = a + b + c
          // Turn the 24 bits into four chunks of 6 bits each, and append the
          // matching character for each of them to the output.
          output += (
            TABLE.charAt(buffer >> 18 & 0x3F) +
						TABLE.charAt(buffer >> 12 & 0x3F) +
						TABLE.charAt(buffer >> 6 & 0x3F) +
						TABLE.charAt(buffer & 0x3F)
          )
        }

        if (padding == 2) {
          a = input.charCodeAt(position) << 8
          b = input.charCodeAt(++position)
          buffer = a + b
          output += (
            TABLE.charAt(buffer >> 10) +
						TABLE.charAt((buffer >> 4) & 0x3F) +
						TABLE.charAt((buffer << 2) & 0x3F) +
						'='
          )
        } else if (padding == 1) {
          buffer = input.charCodeAt(position)
          output += (
            TABLE.charAt(buffer >> 2) +
						TABLE.charAt((buffer << 4) & 0x3F) +
						'=='
          )
        }

        return output
      }

      const base64 = {
        'encode': encode,
        'decode': decode,
        'version': '0.1.0'
      }

      // Some AMD build optimizers, like r.js, check for specific condition patterns
      // like the following:
      if (
        typeof undefined === 'function' &&
				typeof undefined.amd === 'object' &&
				undefined.amd
      ) {
        undefined(function() {
          return base64
        })
      } else if (freeExports && !freeExports.nodeType) {
        if (freeModule) { // in Node.js or RingoJS v0.8.0+
          freeModule.exports = base64
        } else { // in Narwhal or RingoJS v0.7.0-
          for (const key in base64) {
            base64.hasOwnProperty(key) && (freeExports[ key ] = base64[ key ])
          }
        }
      } else { // in Rhino or a web browser
        root.base64 = base64
      }

    }(commonjsGlobal))
  })

  function makeURI(strData, type) {
    return 'data:' + type + ';base64,' + strData
  }

  function fixType(type) {
    type = type.toLowerCase().replace(/jpg/i, 'jpeg')
    const r = type.match(/png|jpeg|bmp|gif/)[ 0 ]

    return 'image/' + r
  }

  function encodeData(data) {
    let str = ''

    if (typeof data === 'string') {
      str = data
    } else {
      for (let i = 0; i < data.length; i++) {
        str += String.fromCharCode(data[ i ])
      }
    }
    return base64.encode(str)
  }

  /**
	 * 获取图像区域隐含的像素数据
	 * @param canvasId canvas标识
	 * @param x 将要被提取的图像数据矩形区域的左上角 x 坐标
	 * @param y 将要被提取的图像数据矩形区域的左上角 y 坐标
	 * @param width 将要被提取的图像数据矩形区域的宽度
	 * @param height 将要被提取的图像数据矩形区域的高度
	 * @param done 完成回调
	 */
  function getImageData(canvasId, x, y, width, height, done) {
    uni.canvasGetImageData({
      canvasId: canvasId,
      x: x,
      y: y,
      width: width,
      height: height,
      success: function success(res) {
        done(res, null)
      },
      fail: function fail(res) {
        done(null, res)
      }
    })
  }

  /**
	 * 生成bmp格式图片
	 * 按照规则生成图片响应头和响应体
	 * @param oData 用来描述 canvas 区域隐含的像素数据 { data, width, height } = oData
	 * @returns {*} base64字符串
	 */
  function genBitmapImage(oData) {
    //
    // BITMAPFILEHEADER: http://msdn.microsoft.com/en-us/library/windows/desktop/dd183374(v=vs.85).aspx
    // BITMAPINFOHEADER: http://msdn.microsoft.com/en-us/library/dd183376.aspx
    //
    const biWidth = oData.width

    const biHeight = oData.height

    const biSizeImage = biWidth * biHeight * 3

    const bfSize = biSizeImage + 54 // total header size = 54 bytes

    //
    //  typedef struct tagBITMAPFILEHEADER {
    //  	WORD bfType;
    //  	DWORD bfSize;
    //  	WORD bfReserved1;
    //  	WORD bfReserved2;
    //  	DWORD bfOffBits;
    //  } BITMAPFILEHEADER;
    //
    const BITMAPFILEHEADER = [
      // WORD bfType -- The file type signature; must be "BM"
      0x42, 0x4D,
      // DWORD bfSize -- The size, in bytes, of the bitmap file
      bfSize & 0xff, bfSize >> 8 & 0xff, bfSize >> 16 & 0xff, bfSize >> 24 & 0xff,
      // WORD bfReserved1 -- Reserved; must be zero
      0, 0,
      // WORD bfReserved2 -- Reserved; must be zero
      0, 0,
      // DWORD bfOffBits -- The offset, in bytes, from the beginning of the BITMAPFILEHEADER structure to the bitmap bits.
      54, 0, 0, 0
    ]

    //
    //  typedef struct tagBITMAPINFOHEADER {
    //  	DWORD biSize;
    //  	LONG  biWidth;
    //  	LONG  biHeight;
    //  	WORD  biPlanes;
    //  	WORD  biBitCount;
    //  	DWORD biCompression;
    //  	DWORD biSizeImage;
    //  	LONG  biXPelsPerMeter;
    //  	LONG  biYPelsPerMeter;
    //  	DWORD biClrUsed;
    //  	DWORD biClrImportant;
    //  } BITMAPINFOHEADER, *PBITMAPINFOHEADER;
    //
    const BITMAPINFOHEADER = [
      // DWORD biSize -- The number of bytes required by the structure
      40, 0, 0, 0,
      // LONG biWidth -- The width of the bitmap, in pixels
      biWidth & 0xff, biWidth >> 8 & 0xff, biWidth >> 16 & 0xff, biWidth >> 24 & 0xff,
      // LONG biHeight -- The height of the bitmap, in pixels
      biHeight & 0xff, biHeight >> 8 & 0xff, biHeight >> 16 & 0xff, biHeight >> 24 & 0xff,
      // WORD biPlanes -- The number of planes for the target device. This value must be set to 1
      1, 0,
      // WORD biBitCount -- The number of bits-per-pixel, 24 bits-per-pixel -- the bitmap
      // has a maximum of 2^24 colors (16777216, Truecolor)
      24, 0,
      // DWORD biCompression -- The type of compression, BI_RGB (code 0) -- uncompressed
      0, 0, 0, 0,
      // DWORD biSizeImage -- The size, in bytes, of the image. This may be set to zero for BI_RGB bitmaps
      biSizeImage & 0xff, biSizeImage >> 8 & 0xff, biSizeImage >> 16 & 0xff, biSizeImage >> 24 & 0xff,
      // LONG biXPelsPerMeter, unused
      0, 0, 0, 0,
      // LONG biYPelsPerMeter, unused
      0, 0, 0, 0,
      // DWORD biClrUsed, the number of color indexes of palette, unused
      0, 0, 0, 0,
      // DWORD biClrImportant, unused
      0, 0, 0, 0
    ]

    const iPadding = (4 - ((biWidth * 3) % 4)) % 4

    const aImgData = oData.data

    let strPixelData = ''

    const biWidth4 = biWidth << 2

    let y = biHeight

    const fromCharCode = String.fromCharCode

    do {
      const iOffsetY = biWidth4 * (y - 1)

      let strPixelRow = ''

      for (let x = 0; x < biWidth; x++) {
        const iOffsetX = x << 2

        strPixelRow += fromCharCode(aImgData[ iOffsetY + iOffsetX + 2 ]) +
					fromCharCode(aImgData[ iOffsetY + iOffsetX + 1 ]) +
					fromCharCode(aImgData[ iOffsetY + iOffsetX ])
      }

      for (let c = 0; c < iPadding; c++) {
        strPixelRow += String.fromCharCode(0)
      }

      strPixelData += strPixelRow
    } while (--y)

    const strEncoded = encodeData(BITMAPFILEHEADER.concat(BITMAPINFOHEADER)) + encodeData(strPixelData)

    return strEncoded
  }

  /**
	 * 转换为图片base64
	 * @param canvasId canvas标识
	 * @param x 将要被提取的图像数据矩形区域的左上角 x 坐标
	 * @param y 将要被提取的图像数据矩形区域的左上角 y 坐标
	 * @param width 将要被提取的图像数据矩形区域的宽度
	 * @param height 将要被提取的图像数据矩形区域的高度
	 * @param type 转换图片类型
	 * @param done 完成回调
	 */
  function convertToImage(canvasId, x, y, width, height, type, done) {
    if (done === void 0) done = function() {}

    if (type === undefined) {
      type = 'png'
    }
    type = fixType(type)
    if (/bmp/.test(type)) {
      getImageData(canvasId, x, y, width, height, function(data, err) {
        const strData = genBitmapImage(data)

        tools_7(done) && done(makeURI(strData, 'image/' + type), err)
      })
    } else {
      console.error('暂不支持生成\'' + type + '\'类型的base64图片')
    }
  }

  const CanvasToBase64 = {
    convertToImage: convertToImage,
    // convertToPNG: function (width, height, done) {
    //   return convertToImage(width, height, 'png', done)
    // },
    // convertToJPEG: function (width, height, done) {
    //   return convertToImage(width, height, 'jpeg', done)
    // },
    // convertToGIF: function (width, height, done) {
    //   return convertToImage(width, height, 'gif', done)
    // },
    convertToBMP: function(ref, done) {
      if (ref === void 0) ref = {}
      const canvasId = ref.canvasId

      const x = ref.x

      const y = ref.y

      const width = ref.width

      const height = ref.height

      if (done === void 0) done = function() {}

      return convertToImage(canvasId, x, y, width, height, 'bmp', done)
    }
  }

  function methods() {
    const self = this

    const boundWidth = self.width // 裁剪框默认宽度，即整个画布宽度

    const boundHeight = self.height // 裁剪框默认高度，即整个画布高度

    const id = self.id

    const targetId = self.targetId

    const pixelRatio = self.pixelRatio

    const ref = self.cut

    let x = ref.x

    if (x === void 0) x = 0
    let y = ref.y

    if (y === void 0) y = 0
    let width = ref.width

    if (width === void 0) width = boundWidth
    let height = ref.height

    if (height === void 0) height = boundHeight

    self.updateCanvas = function(done) {
      if (self.croperTarget) {
        //  画布绘制图片
        self.ctx.drawImage(
          self.croperTarget,
          self.imgLeft,
          self.imgTop,
          self.scaleWidth,
          self.scaleHeight
        )
      }
      tools_7(self.onBeforeDraw) && self.onBeforeDraw(self.ctx, self)

      self.setBoundStyle(self.boundStyle) //	设置边界样式

      self.ctx.draw(false, done)
      return self
    }

    self.pushOrigin = self.pushOrign = function(src) {
      self.src = src

      tools_7(self.onBeforeImageLoad) && self.onBeforeImageLoad(self.ctx, self)

      return getImageInfo({
        src: src
      })
        .then(function(res) {
          const innerAspectRadio = res.width / res.height

          const customAspectRadio = width / height

          self.croperTarget = res.path

          if (innerAspectRadio < customAspectRadio) {
            self.rectX = x
            self.baseWidth = width
            self.baseHeight = width / innerAspectRadio
            self.rectY = y - Math.abs((height - self.baseHeight) / 2)
          } else {
            self.rectY = y
            self.baseWidth = height * innerAspectRadio
            self.baseHeight = height
            self.rectX = x - Math.abs((width - self.baseWidth) / 2)
          }

          self.imgLeft = self.rectX
          self.imgTop = self.rectY
          self.scaleWidth = self.baseWidth
          self.scaleHeight = self.baseHeight

          self.update()

          return new Promise(function(resolve) {
            self.updateCanvas(resolve)
          })
        })
        .then(function() {
          tools_7(self.onImageLoad) && self.onImageLoad(self.ctx, self)
        })
    }

    self.removeImage = function() {
      self.src = ''
      self.croperTarget = ''
      return draw(self.ctx)
    }

    self.getCropperBase64 = function(done) {
      if (done === void 0) done = function() {}

      CanvasToBase64.convertToBMP({
        canvasId: id,
        x: x,
        y: y,
        width: width,
        height: height
      }, done)
    }

    self.getCropperImage = function(opt, fn) {
      const customOptions = opt

      let canvasOptions = {
        canvasId: id,
        x: x,
        y: y,
        width: width,
        height: height
      }

      let task = function() {
        return Promise.resolve()
      }

      if (
        tools_10(customOptions) &&
				customOptions.original
      ) {
        // original mode
        task = function() {
          self.targetCtx.drawImage(
            self.croperTarget,
            self.imgLeft * pixelRatio,
            self.imgTop * pixelRatio,
            self.scaleWidth * pixelRatio,
            self.scaleHeight * pixelRatio
          )

          canvasOptions = {
            canvasId: targetId,
            x: x * pixelRatio,
            y: y * pixelRatio,
            width: width * pixelRatio,
            height: height * pixelRatio
          }

          return draw(self.targetCtx)
        }
      }

      return task()
        .then(function() {
          if (tools_10(customOptions)) {
            canvasOptions = Object.assign({}, canvasOptions, customOptions)
          }

          if (tools_7(customOptions)) {
            fn = customOptions
          }

          const arg = canvasOptions.componentContext
            ? [canvasOptions, canvasOptions.componentContext]
            : [canvasOptions]

          return canvasToTempFilePath.apply(null, arg)
        })
        .then(function(res) {
          const tempFilePath = res.tempFilePath

          return tools_7(fn)
            ? fn.call(self, tempFilePath, null)
            : tempFilePath
        })
        .catch(function(err) {
          if (tools_7(fn)) {
            fn.call(self, null, err)
          } else {
            throw err
          }
        })
    }
  }

  /**
	 * 获取最新缩放值
	 * @param oldScale 上一次触摸结束后的缩放值
	 * @param oldDistance 上一次触摸结束后的双指距离
	 * @param zoom 缩放系数
	 * @param touch0 第一指touch对象
	 * @param touch1 第二指touch对象
	 * @returns {*}
	 */
  const getNewScale = function(oldScale, oldDistance, zoom, touch0, touch1) {
    let xMove, yMove, newDistance
    // 计算二指最新距离

    xMove = Math.round(touch1.x - touch0.x)
    yMove = Math.round(touch1.y - touch0.y)
    newDistance = Math.round(Math.sqrt(xMove * xMove + yMove * yMove))

    return oldScale + 0.001 * zoom * (newDistance - oldDistance)
  }

  function update() {
    const self = this

    if (!self.src) {
      return
    }

    self.__oneTouchStart = function(touch) {
      self.touchX0 = Math.round(touch.x)
      self.touchY0 = Math.round(touch.y)
    }

    self.__oneTouchMove = function(touch) {
      let xMove, yMove
      // 计算单指移动的距离

      if (self.touchended) {
        return self.updateCanvas()
      }
      xMove = Math.round(touch.x - self.touchX0)
      yMove = Math.round(touch.y - self.touchY0)

      const imgLeft = Math.round(self.rectX + xMove)

      const imgTop = Math.round(self.rectY + yMove)

      self.outsideBound(imgLeft, imgTop)

      self.updateCanvas()
    }

    self.__twoTouchStart = function(touch0, touch1) {
      let xMove, yMove, oldDistance

      self.touchX1 = Math.round(self.rectX + self.scaleWidth / 2)
      self.touchY1 = Math.round(self.rectY + self.scaleHeight / 2)

      // 计算两指距离
      xMove = Math.round(touch1.x - touch0.x)
      yMove = Math.round(touch1.y - touch0.y)
      oldDistance = Math.round(Math.sqrt(xMove * xMove + yMove * yMove))

      self.oldDistance = oldDistance
    }

    self.__twoTouchMove = function(touch0, touch1) {
      const oldScale = self.oldScale

      const oldDistance = self.oldDistance

      const scale = self.scale

      const zoom = self.zoom

      self.newScale = getNewScale(oldScale, oldDistance, zoom, touch0, touch1)

      //  设定缩放范围
      self.newScale <= 1 && (self.newScale = 1)
      self.newScale >= scale && (self.newScale = scale)

      self.scaleWidth = Math.round(self.newScale * self.baseWidth)
      self.scaleHeight = Math.round(self.newScale * self.baseHeight)
      const imgLeft = Math.round(self.touchX1 - self.scaleWidth / 2)

      const imgTop = Math.round(self.touchY1 - self.scaleHeight / 2)

      self.outsideBound(imgLeft, imgTop)

      self.updateCanvas()
    }

    self.__xtouchEnd = function() {
      self.oldScale = self.newScale
      self.rectX = self.imgLeft
      self.rectY = self.imgTop
    }
  }

  const handle = {
    //  图片手势初始监测
    touchStart: function touchStart(e) {
      const self = this

      const ref = e.touches

      const touch0 = ref[ 0 ]

      const touch1 = ref[ 1 ]

      if (!self.src) {
        return
      }

      setTouchState(self, true, null, null)

      // 计算第一个触摸点的位置，并参照改点进行缩放
      self.__oneTouchStart(touch0)

      // 两指手势触发
      if (e.touches.length >= 2) {
        self.__twoTouchStart(touch0, touch1)
      }
    },

    //  图片手势动态缩放
    touchMove: function touchMove(e) {
      const self = this

      const ref = e.touches

      const touch0 = ref[ 0 ]

      const touch1 = ref[ 1 ]

      if (!self.src) {
        return
      }

      setTouchState(self, null, true)

      // 单指手势时触发
      if (e.touches.length === 1) {
        self.__oneTouchMove(touch0)
      }
      // 两指手势触发
      if (e.touches.length >= 2) {
        self.__twoTouchMove(touch0, touch1)
      }
    },

    touchEnd: function touchEnd(e) {
      const self = this

      if (!self.src) {
        return
      }

      setTouchState(self, false, false, true)
      self.__xtouchEnd()
    }
  }

  function cut() {
    const self = this

    const boundWidth = self.width // 裁剪框默认宽度，即整个画布宽度

    const boundHeight = self.height
    // 裁剪框默认高度，即整个画布高度

    const ref = self.cut

    let x = ref.x

    if (x === void 0) x = 0
    let y = ref.y

    if (y === void 0) y = 0
    let width = ref.width

    if (width === void 0) width = boundWidth
    let height = ref.height

    if (height === void 0) height = boundHeight

    /**
		 * 设置边界
		 * @param imgLeft 图片左上角横坐标值
		 * @param imgTop 图片左上角纵坐标值
		 */
    self.outsideBound = function(imgLeft, imgTop) {
      self.imgLeft = imgLeft >= x
        ? x
        : self.scaleWidth + imgLeft - x <= width
          ? x + width - self.scaleWidth
          : imgLeft

      self.imgTop = imgTop >= y
        ? y
        : self.scaleHeight + imgTop - y <= height
          ? y + height - self.scaleHeight
          : imgTop
    }

    /**
		 * 设置边界样式
		 * @param color	边界颜色
		 */
    self.setBoundStyle = function(ref) {
      if (ref === void 0) ref = {}
      let color = ref.color

      if (color === void 0) color = '#04b00f'
      let mask = ref.mask

      if (mask === void 0) mask = 'rgba(0, 0, 0, 0.3)'
      let lineWidth = ref.lineWidth

      if (lineWidth === void 0) lineWidth = 1

      const half = lineWidth / 2

      const boundOption = [{
        start: {
          x: x - half,
          y: y + 10 - half
        },
        step1: {
          x: x - half,
          y: y - half
        },
        step2: {
          x: x + 10 - half,
          y: y - half
        }
      },
      {
        start: {
          x: x - half,
          y: y + height - 10 + half
        },
        step1: {
          x: x - half,
          y: y + height + half
        },
        step2: {
          x: x + 10 - half,
          y: y + height + half
        }
      },
      {
        start: {
          x: x + width - 10 + half,
          y: y - half
        },
        step1: {
          x: x + width + half,
          y: y - half
        },
        step2: {
          x: x + width + half,
          y: y + 10 - half
        }
      },
      {
        start: {
          x: x + width + half,
          y: y + height - 10 + half
        },
        step1: {
          x: x + width + half,
          y: y + height + half
        },
        step2: {
          x: x + width - 10 + half,
          y: y + height + half
        }
      }]

      // 绘制半透明层
      self.ctx.beginPath()
      self.ctx.setFillStyle(mask)
      self.ctx.fillRect(0, 0, x, boundHeight)
      self.ctx.fillRect(x, 0, width, y)
      self.ctx.fillRect(x, y + height, width, boundHeight - y - height)
      self.ctx.fillRect(x + width, 0, boundWidth - x - width, boundHeight)
      self.ctx.fill()

      boundOption.forEach(function(op) {
        self.ctx.beginPath()
        self.ctx.setStrokeStyle(color)
        self.ctx.setLineWidth(lineWidth)
        self.ctx.moveTo(op.start.x, op.start.y)
        self.ctx.lineTo(op.step1.x, op.step1.y)
        self.ctx.lineTo(op.step2.x, op.step2.y)
        self.ctx.stroke()
      })
    }
  }

  const version = '1.3.9'

  const WeCropper = function WeCropper(params) {
    const self = this

    const _default = {}

    validator(self, DEFAULT)

    Object.keys(DEFAULT).forEach(function(key) {
      _default[ key ] = DEFAULT[ key ].default
    })
    Object.assign(self, _default, params)

    self.prepare()
    self.attachPage()
    self.createCtx()
    self.observer()
    self.cutt()
    self.methods()
    self.init()
    self.update()

    return self
  }

  WeCropper.prototype.init = function init() {
    const self = this

    const src = self.src

    self.version = version

    typeof self.onReady === 'function' && self.onReady(self.ctx, self)

    if (src) {
      self.pushOrign(src)
    } else {
      self.updateCanvas()
    }
    setTouchState(self, false, false, false)

    self.oldScale = 1
    self.newScale = 1

    return self
  }

  Object.assign(WeCropper.prototype, handle)

  WeCropper.prototype.prepare = prepare
  WeCropper.prototype.observer = observer
  WeCropper.prototype.methods = methods
  WeCropper.prototype.cutt = cut
  WeCropper.prototype.update = update

  return WeCropper

}))
